

/*
 *******************************************************************************
 * LCDscope : a oscilloscope on a 20x4 text LCD
 * also works with a standard 16x4 text LCD
 *******************************************************************************
 *
 * This program shows how to use custom chars on a text LCD to build a tiny graphic screen
 * To illustrate this, here is a quick & dirty mini oscilloscope.
 *
 * Author : Bruno Gavand, February 2009
 * see more details on http://www.micro-examples.com/
 *
 * source code for mikro C compiler V8.2
 * feel free to use this code at your own risks
 *

 *******************************************************************************
 */

/*********************
 * CONSTANTS
 *********************/
#include <LiquidCrystal.h>

// initialize the library with the numbers of the interface pins
LiquidCrystal lcd(8, 9, 4, 5, 6, 7);
volatile boolean l;


// if you have a 16x2 standard LCD, untag this line :
#define LCD16x2
// otherwise, you are supposed to have the nice 20x4 LCD
#ifndef LCD16x2
#define LCD20x4
#endif

#define C_X             4               // number of columns in pseudo-graphic screen
#define C_Y             2               // number of rows in pseudo-graphic screen

#define miniGLCD_x      (5*C_X)         // number of X pixels in pseudo-graphic screen
#define miniGLCD_y      (8*C_Y)         // number of Y pixels in pseudo-graphic screen

#define LEVEL           16              // trigger sensitivity (number of ADC points)

// colors of pseudo-graphic screen
#define miniGLCD_COLOR_WHITE         0
#define miniGLCD_COLOR_BLACK         1
#define miniGLCD_COLOR_REVERSE       2

// pseudo-graphic function prototypes
void    miniGLCD_fill(unsigned char c) ;
void    miniGLCD_setPixel(char x, char y, unsigned char mode) ;
void    miniGLCD_line(char x0, char y0, char x1, char y1, unsigned char pcolor) ;
unsigned char miniGLCD_getPixel(char x, char y) ;

/***************
 * RAM VARIABLES
 ***************/

// horizontal frequency structure
struct TIMEBASE
        {
        unsigned char   t0con ;         // timer 0 configuration
        unsigned char   period[8] ;     // period value
        unsigned char   unit ;          // period unit
        } timeBase[] =
                {
                        {       0b10000100, "1.04857", ' ' },
                        {       0b10000011, "524.288", 'm' },
                        {       0b10000010, "262.144", 'm' },
                        {       0b10000001, "131.072", 'm' },
                        {       0b10000000, "65.536 ", 'm' },
                        {       0b11000111, "32.768 ", 'm' },
                        {       0b11000110, "16.384 ", 'm' },
                        {       0b11000101, "8.192  ", 'm' },
                        {       0b11000100, "4.096  ", 'm' },
                        {       0b11000011, "2.048  ", 'm' },
                        {       0b11000010, "1.024  ", 'm' },
                        {       0b11000001, "512    ", '\xe4' },
                        {       0b11000000, "256    ", '\xe4' },
                        {       0b11001000, "128    ", '\xe4' }
                } ;
unsigned char   tbase = 0 ;             // current timebase index

// vertical input range structure
struct AINPUT
        {
        unsigned char   div ;           // power of 2 of input divider
        const char   *ampl ;         // range value in volts
        } Anginput[] =
                {
                        {       4, "2.500" },
                        {       2, "1.250" },
                        {       1, "0.625" },
                } ;
 
unsigned char   ipt = 0 ;               // current input range index
unsigned char   vdiv ;                  // current power of 2 of input divider

#ifdef LCD20x4
// scrolling message
unsigned char msg[][8] =
        {
        "       ",
        "       ",
        "       ",
        "       ",
        "  LCD  ",
        " SCOPE ",
        "  By   ",
        "BrunoG ",
        "*      ",
        "       ",
        "       ",
        "  see  ",
        " more  ",
        "details",
        "  and  ",
        "dwnload",
        "mikroC ",
        "source ",
        "code on",
        "       ",
        "www.   ",
        "micro- ",
        "example",
        "s.com  ",
        "*      ",
        "       ",
        "       ",
        "20x4LCD",
        "Black/ ",
        "Yellow ",
        "BkLight",
        "specs.&",
        "price :",
        "       ",
        "www.   ",
        "circuit",
        "-ed.com",
        "*      ",
        "       ",
        "       ",
        " Thank ",
        "  you  ",
        "Warren!",
        ""
        } ;
unsigned char   firstMsg = 0 ;          // scrolling message index
#endif

unsigned char   miniGLCD_screen[C_X * C_Y * 8] ;        // pseudo-screen bitmap

unsigned char   samples[miniGLCD_x] ;   // sample table
unsigned char   sIdx = 0 ;              // sample index

unsigned char   trigger = 0 ;           // trigger status
unsigned char   trigValue = 0 ;         // trigger value

unsigned char   hold = 0 ;              // hold screen flag

unsigned int    t0ctr ;                 // timer 0 overflow counter

/****************************
 * INTERRUPT ROUTINE
 ****************************/
//TC1 ch 0
void TC3_Handler()
        {
        noInterrupts();  
digitalWrite(13, l = !l);
        // only timer 0 overflow is due to service
        if(TC_GetStatus(TC1, 0))
                {
        Serial.println("Start of timer service routin\n");

                if(sIdx < miniGLCD_x)                   // is sampling in progress ?
                        {
                        if(trigger == 2)                // is sampling triggered ?
                                {
                                // read ADC sample, adjust to range and store to sample buffer
                                samples[sIdx++] = miniGLCD_y - (analogRead(A1) >> vdiv) ;
                                }
                        else if(trigger == 1)           // maximum was detected
                                {
                                // is signal rising down ?
                                if((trigValue > LEVEL) && (analogRead(A1) < trigValue - LEVEL))
                                        {
                                        // yes, triggers sampling
                                        trigger = 2 ;
                                        }
                                else
                                        {
                                        // no, update maximum value
                                        if(analogRead(A1) > trigValue)
                                                {
                                                trigValue = analogRead(A1) ;
                                                }
                                        }
                                }
                        else                            // looking for maximum
                                {
                                // is signal rising up ?
                                if((trigValue < 255 - LEVEL) && (analogRead(A1) > trigValue + LEVEL))
                                        {
                                        // yes, next step is to wait for signal rising down
                                        trigger = 1 ;
                                        trigValue = 0 ;
                                        }
                                else
                                        {
                                        // no, update minimum value
                                        if(analogRead(A1) < trigValue)
                                                {
                                                trigValue = analogRead(A1) ;
                                                }
                                        }
                                }
                                
                        // start ADC, no sampling is required since ADC
                        // is always connected to the same input
                        //ADCON0.GO = 1 ;
                        }

               // t0ctr++ ;
               // INTCON.TMR0IF = 0 ;
                }
          Serial.println("exiting timer service routin\n");

           interrupts();     
        }
        
        
        
        
void startTimer(Tc *tc, uint32_t channel, IRQn_Type irq, uint32_t frequency) {

  pmc_set_writeprotect(false);
  pmc_enable_periph_clk((uint32_t)irq);
  TC_Configure(tc, channel, TC_CMR_WAVE | TC_CMR_WAVSEL_UP_RC | TC_CMR_TCCLKS_TIMER_CLOCK4);
  uint32_t rc = VARIANT_MCK/128/frequency; //128 because we selected TIMER_CLOCK4 above
  TC_SetRA(tc, channel, rc/2); //50% high, 50% low
  TC_SetRC(tc, channel, rc);
  TC_Start(tc, channel);
  tc->TC_CHANNEL[channel].TC_IER=TC_IER_CPCS;
  tc->TC_CHANNEL[channel].TC_IDR=~TC_IER_CPCS;
  NVIC_EnableIRQ(irq);
}



/*******************************
 * UPDATE SETTINGS & DRAW SCREEN
 *******************************/
void    mkScreen()
        { 
        Serial.println("mkscreen start\n");
        //Serial.println(VARIANT_MCK/128/timeBase[tbase].t0con);
          
        //T0CON = timeBase[tbase].t0con ;                         // new timer 0 settings
        vdiv = Anginput[ipt].div ;                                 // store input divider

        // ADC settings
        //ADCON1 = 0b00001110 ;
        //ADCON0 = 0b11000001 ;

#ifdef  LCD20x4
        LCD_out(1, 14, timeBase[tbase].period) ;                // display period value
        LCD_chr(2, 15, timeBase[tbase].unit) ;                  // display period unit

        LCD_out(3, 16, ( unsigned char   *)Anginput[ipt].ampl) ;                       // display input rangeg

        // hold screen ?
        LCD_out(1, 9, hold ? ( unsigned char   *)"Hold" : ( unsigned char   *)"\xff\xff\xff\xff") ;
#else
        LCD_out(1, 12, timeBase[tbase].period) ;                // display period value
        LCD_chr(2, 12, timeBase[tbase].unit) ;                  // display period unit

        LCD_out(1, 1,( unsigned char   *) Anginput[ipt].ampl) ;                       // display input range

        // hold screen ?
        LCD_out(1, 6, hold ? ( unsigned char   *)"H" : ( unsigned char   *)"\xff") ;
#endif
        Serial.println("mkscreen exit\n");

        }

/************************
 * UPDATE SCREEN AND DEBOUNCE PORTB KEYS
 ************************/
void    debounce()
        {
        mkScreen() ;
        
        delay(20) ;
        Serial.println("debounce exit\n");
        //Serial.println(analogRead(A0));
        //while(analogRead(A0) != 0xff) ;
        delay(20) ;
        }

/***********************
 * PSEUDO GRAPHIC FUNCTIONS
 ***********************/
 
/************************************************
 * miniGLCD_fill : fill graphic screen with pattern
 * parameters :
 *      c : filling pattern
 *              for example : 0 for black screen, 0xff for white screen
 * returns :
 *      nothing
 * requires :
 *      miniGLCD_init must have been called
 * notes :
 *      none
 */
void    miniGLCD_fill(unsigned char c)
        {
        memset(miniGLCD_screen, c, sizeof(miniGLCD_screen)) ;
        }

/********************************************
 * miniGLCD_setPixel : write pixel
 * parameters :
 *      x : pixel row
 *      y : pixel column
 *      mode : miniGLCD_COLOR_WHITE or miniGLCD_COLOR_BLACK or miniGLCD_COLOR_REVERSE
 * returns :
 *      nothing
 * requires :
 *      miniGLCD_init must have been called
 * notes :
 *      none
 */
void    miniGLCD_setPixel(char x, char y, unsigned char mode)
        {
        unsigned char *ptr ;
        unsigned char mask ;

        /*
         * do nothing if pixel is out of bounds
         */
        if(x < 0) return ;
        if(y < 0) return ;
        if(x > miniGLCD_x) return ;
        if(y > miniGLCD_y) return ;

        ptr = miniGLCD_screen + (((y * (C_X * 8)) + x) / 8) ;      // points to byte in screen map
        mask = 1 << (x & 7) ;                           // pixel bit mask

        switch(mode)
                {
                case miniGLCD_COLOR_BLACK:
                        *ptr &= ~mask ;                 // clear bit
                        break ;
                case miniGLCD_COLOR_WHITE:                   // set bit
                        *ptr |= mask ;
                        break ;
                default:
                        *ptr ^= mask ;                  // toggle bit
                        break ;
                }
        }
        
void changeTimbase(){
  
  noInterrupts();
        uint32_t rc = VARIANT_MCK/128/tbase;  //timeBase[tbase].t0con; //128 because we selected TIMER_CLOCK4 above
        TC_SetRA(TC1, 0, rc/2); //50% high, 50% low
        TC_SetRC(TC1, 0, rc);
        TC_Start(TC1, 0);
   interrupts();     
}


/********************************************
 * miniGLCD_setPixel : read pixel
 * parameters :
 *      x : pixel row
 *      y : pixel column
 * returns :
 *      color of pixel at (x, y)
 * requires :
 *      miniGLCD_init must have been called
 * notes :
 *      none
 */
unsigned char miniGLCD_getPixel(char x, char y)
        {
        unsigned char *ptr ;
        unsigned char mask ;

        /*
         * do nothing if pixel is out of bounds
         */
        if(x < 0) return(0) ;
        if(y < 0) return(0) ;
        if(x > miniGLCD_x) return(0) ;
        if(y > miniGLCD_y) return(0) ;

        ptr = miniGLCD_screen + (((y * (C_X * 8)) + x) / 8) ;      // points to byte in screen map
        mask = 1 << (x & 7) ;                           // pixel bit mask

        return(*ptr & mask) ;
        }

/******************************
 * miniGLCD_line : draw a line
 * parameters :
 *      x0, y0 : pixel start coordinates
 *      x1, y1 : pixel end coordinates
 *      pcolor : miniGLCD_COLOR_WHITE or miniGLCD_COLOR_BLACK or miniGLCD_COLOR_REVERSE
 * returns :
 *      nothing
 * requires :
 *      miniGLCD_init must have been called
 * notes :
 *      uses Bresenham's line drawing algorithm
 */
void miniGLCD_line(char x0, char y0, char x1, char y1, unsigned char pcolor)
        {
        int     dy ;
        int     dx ;
        int     stepx, stepy ;

        dy = y1 - y0 ;
        dx = x1 - x0 ;

        if(dy < 0)
                {
                dy = -dy ;
                stepy = -1 ;
                }
        else
                {
                stepy = 1 ;
                }

        if(dx < 0)
                {
                dx = -dx ;
                stepx = -1 ;
                }
        else
                {
                stepx = 1 ;
                }

        dy <<= 1 ;
        dx <<= 1 ;

        miniGLCD_setPixel(x0, y0, pcolor) ;

        if(dx > dy)
                {
                int fraction = dy - (dx >> 1) ;

                while(x0 != x1)
                        {
                        if(fraction >= 0)
                                {
                                y0 += stepy ;
                                fraction -= dx ;
                                }
                        x0 += stepx ;
                        fraction += dy ;
                        miniGLCD_setPixel(x0, y0, pcolor) ;
                        }
                }
        else
                {
                int fraction = dx - (dy >> 1) ;

                while(y0 != y1)
                        {
                        if(fraction >= 0)
                                {
                                x0 += stepx ;
                                fraction -= dy ;
                                }
                        y0 += stepy ;
                        fraction += dx ;
                        miniGLCD_setPixel(x0, y0, pcolor) ;
                        }
                }
        }

/*************************************************************
 * program custom character n at line pos_row column pos_char
 * if mode is not zero, also write custom char to LCD
 */
void CustomChar(unsigned char mode, unsigned char n, char pos_row, char pos_char)
        {
        unsigned char    i, j ;
        Serial.println("building custom char\n");

        LCD_Cmd(64 + n * 8) ;
        for(i = 0 ; i < 8 ; i++)
                {
                unsigned char bm = 0 ;

                for(j = 0 ; j < 5 ; j++)
                        {
                        bm <<= 1 ;
                        bm |= miniGLCD_getPixel(pos_char * 5 + j, pos_row * 8 + i) ? 1 : 0 ;
                        }
                LCD_Chr_Cp(bm) ;
                }
        LCD_hme() ;
#ifdef LCD20x4
        if(mode) LCD_chr(pos_row + 2, pos_char + 9, n) ;
#else
        if(mode) LCD_chr(pos_row + 1, pos_char + 7, n) ;
#endif
        }

/******************
 * MAIN LOOP
 ******************/
void    setup()
        {

pinMode(13,OUTPUT);
        startTimer(TC1, 0, TC3_IRQn, 5); //TC1 channel 0, the IRQ for that channel and the desired frequency
        Serial.begin(9600);

        // LCD configuration
       lcd.begin(16,2) ;                      // Initialize LCD connected to PORTD
       lcd.clear() ;                   // Clear display
       lcd.noCursor() ;              // Turn cursor off

        // display layout
#ifdef LCD20x4
        LCD_out(1, 8, ( unsigned char*)"\xff\xff\xff\xff\xff\xff") ;
        LCD_out(2, 8,( unsigned char*) "\xff    \xff  s/Div") ;
        LCD_out(3, 8, ( unsigned char*)"\xff    \xff") ;
        LCD_out(4, 8, ( unsigned char*)"\xff\xff\xff\xff\xff\xff  V/Div") ;
#else
        LCD_out(1, 1, ( unsigned char   *)"     \xff    \xff") ;
        LCD_out(2, 1, ( unsigned char   *)"V/Div\xff    \xff s/Div") ;
#endif

        // send custom chars
        CustomChar(1, 0, 0, 0) ;
        CustomChar(1, 1, 0, 1) ;
        CustomChar(1, 2, 0, 2) ;
        CustomChar(1, 3, 0, 3) ;
        CustomChar(1, 4, 1, 0) ;
        CustomChar(1, 5, 1, 1) ;
        CustomChar(1, 6, 1, 2) ;
        CustomChar(1, 7, 1, 3) ;
        
     
        Serial.println("end of setup\n");
        
      }
        
        
        
  void   loop(){
        unsigned int  wait ;
        unsigned char i, j ;
        mkScreen() ;

        //for(;;)                 // forever
        //        {
         Serial.println("start of main loop\n");
               // if not in hold mode and samples buffer is full
                if((hold == 0) && (sIdx == miniGLCD_x))
                        {
                        // clear pseudo-screen
                        miniGLCD_fill(0) ;

                        // draw wave
                        for(i = 0 ; i < miniGLCD_x - 1 ; i++)
                                {
                                j = i + 1 ;

                                miniGLCD_line(i, samples[i], j, samples[j], miniGLCD_COLOR_WHITE) ;
                                }

                        // program custom chars
                        CustomChar(0, 0, 0, 0) ;
                        CustomChar(0, 1, 0, 1) ;
                        CustomChar(0, 2, 0, 2) ;
                        CustomChar(0, 3, 0, 3) ;
                        CustomChar(0, 4, 1, 0) ;
                        CustomChar(0, 5, 1, 1) ;
                        CustomChar(0, 6, 1, 2) ;
                        CustomChar(0, 7, 1, 3) ;

                        // restart trigger and samples index
                        trigValue = 255 ;
                        trigger = 0 ;
                        sIdx = 0 ;
                        }

                // change horizontal frequency
                if(analogRead(A0)< 110)
                        {
                        tbase++ ;
                        if(tbase == sizeof(timeBase) / sizeof(struct TIMEBASE))
                                {
                                tbase = 0 ;
                                }
           Serial.println("custom complete\n");
                        hold = 0 ;
                        changeTimbase();
                        debounce() ;
                     }
                else if(analogRead(A0) <250)
                        {
                        if(tbase == 0)
                                {
                                tbase = sizeof(timeBase) / sizeof(struct TIMEBASE) - 1 ;
                                }
                        else
                                {
                                tbase-- ;
                                }
                        changeTimbase();
                        hold = 0 ;
                        debounce() ;
                        }

                // change vertical range
                else if(analogRead(A0) < 500)
                        {
                        ipt++ ;
                        if(ipt == sizeof(Anginput) / sizeof(struct AINPUT))
                                {
                                ipt = 0 ;
                                }
                        hold = 0 ;
                        debounce() ;
                        }
                else if(analogRead(A0) < 600)
                        {
                        if(ipt == 0)
                                {
                                ipt = sizeof(Anginput) / sizeof(struct AINPUT) - 1 ;
                                }
                        else
                                {
                                ipt-- ;
                                }
                        hold = 0 ;
                        debounce() ;
                        }

                // hold/release screen
                else if(analogRead(A0) < 800)
                        {
                        hold ^= 1 ;
                        debounce() ;
                        }
        Serial.println("Exiting button monitor\n");

                // scrolling message
#ifdef LCD20x4
                if(wait)
                        {
                        if(t0ctr > (1u << (tbase + 5)))
                                {
                                firstMsg++ ;
                                if(msg[firstMsg][0] == 0)
                                        {
                                        firstMsg = 0 ;
                                        }

                                t0ctr = 0 ;
                                wait = 0 ;
                                }
                        }
                else if(t0ctr > (1u << (tbase + 1)))
                        {
                        j = firstMsg ;
                        for(i = 1 ; i <= 4 ; i++)
                                {
                                if((i == 4) && (msg[j + 1][0] == '*'))
                                        {
                                        wait++ ;
                                        }
                                if(msg[j][0] == '*')
                                        {
                                        LCD_out(i, 1,(unsigned char *) "       ") ;
                                        }
                                else
                                        {
                                        LCD_out(i, 1, msg[j]) ;
                                        }

                                j++ ;
                                if(msg[j][0] == 0)
                                        {
                                        j = 0 ;
                                        }
                                }

                        firstMsg++ ;
                        if(msg[firstMsg][0] == 0)
                                {
                                firstMsg = 0 ;
                                }

                        t0ctr = 0 ;
                        }
#endif
                //}
        }
        
void LCD_out(unsigned char row, unsigned char col, unsigned char * dat){
    lcd.setCursor(col-1,row-1);
    lcd.print((char *)dat);
}

void LCD_chr(unsigned char row, unsigned char col, unsigned char dat){
    lcd.setCursor(col-1, row-1);
    lcd.write(dat);
}    

void LCD_Cmd(unsigned char dat){
     lcd.command(dat);
}

void LCD_Chr_Cp(unsigned char dat){
     lcd.write(dat);
}
void LCD_hme(){
     lcd.home();
}        


